﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.Drawing;
using System.Xml.Linq;

namespace Djs.Common.UI
{
    /// <summary>
    /// Třída, která obsahuje extenze .NET tříd = rozšíření základní funkcionality
    /// </summary>
    public static class NetExtensions
    {
        #region String
        /// <summary>
        /// <para>
        /// Hledá další výskyt některého ze znaků (searchFor).
        /// Hledá od pozice (index + 1) včetně, směrem ke konci. Znak na pozici (index) tedy neprohledává.
        /// Pokud na vstupu je index == null, pak nehledá a vrací false (index ponechává null).
        /// Pokud najde některý hledaný znak, pak do parametru index vloží jeho index a vrátí true.
        /// Pokud žádný znak nenajde, pak do parametru index vloží null a vrátí false.
        /// </para>
        /// <para>
        /// Metoda je tedy vhodná k postupnému prohledávání jednoho dlouhého stringu.
        /// Před prvním hledáním má být index iniciovaný na hodnotu -1.
        /// </para>
        ///  <para>Pokud this string je null, pak se chová jako by nic nenašel.</para>
        /// </summary>
        /// <param name="text"></param>
        /// <param name="searchFor"></param>
        /// <param name="index"></param>
        /// <returns></returns>
        public static Int32? SearchNext(this string text, char[] searchFor, int index)
        {
            if (text == null || text.Length == 0)
                return null;

            int from = index;
            if (from < 0) from = 0;
            if (from >= text.Length)
                return null;

            int next = text.IndexOfAny(searchFor, from);
            if (next < 0)
                return null;

            return next;
        }
        #endregion
        #region Rectangle
        public static Point Center(this Rectangle rectangle)
        {
            return new Point(rectangle.X + rectangle.Width / 2, rectangle.Y + rectangle.Height / 2);
        }
        #endregion
        #region Point
        public static Rectangle FromCenter(this Point center, Size size)
        {
            return new Rectangle(new Point(center.X - size.Width / 2, center.Y - size.Height / 2), size);
        }
        /// <summary>
        /// Vrátí bod result = (this + add)
        /// </summary>
        /// <param name="point"></param>
        /// <param name="add"></param>
        /// <returns></returns>
        public static Point Add(this Point point, Point add)
        {
            return new Point(point.X + add.X, point.Y + add.Y);
        }
        /// <summary>
        /// Vrátí bod result = (this - sub)
        /// </summary>
        /// <param name="point"></param>
        /// <param name="sub"></param>
        /// <returns></returns>
        public static Point Sub(this Point point, Point sub)
        {
            return new Point(point.X - sub.X, point.Y - sub.Y);
        }
        #endregion
        #region Size
        /// <summary>
        /// Zarovná prostor dané velikosti do daného hostitele v daném zarovnání
        /// </summary>
        /// <param name="size"></param>
        /// <param name="bounds"></param>
        /// <param name="alignment"></param>
        /// <returns></returns>
        public static Rectangle AlignTo(this Size size, Rectangle bounds, ContentAlignment alignment, bool cropSize)
        {
            Size realSize = size;
            if (cropSize)
            {
                if (realSize.Width > bounds.Width)
                    realSize.Width = bounds.Width;
                if (realSize.Height > bounds.Height)
                    realSize.Height = bounds.Height;
            }
            return realSize.AlignTo(bounds, alignment);
        }
        /// <summary>
        /// Zarovná prostor dané velikosti do daného hostitele v daném zarovnání
        /// </summary>
        /// <param name="size"></param>
        /// <param name="bounds"></param>
        /// <param name="alignment"></param>
        /// <returns></returns>
        public static Rectangle AlignTo(this Size size, Rectangle bounds, ContentAlignment alignment)
        {
            int x = bounds.X;
            int y = bounds.Y;
            int w = bounds.Width - size.Width;
            int h = bounds.Height - size.Height;
            switch (alignment)
            {
                case ContentAlignment.TopLeft:
                    break;
                case ContentAlignment.TopCenter:
                    x += w / 2;
                    break;
                case ContentAlignment.TopRight:
                    x += w;
                    break;
                case ContentAlignment.MiddleLeft:
                    y += h / 2;
                    break;
                case ContentAlignment.MiddleCenter:
                    x += w / 2;
                    y += h / 2;
                    break;
                case ContentAlignment.MiddleRight:
                    x += w;
                    y += h / 2;
                    break;
                case ContentAlignment.BottomLeft:
                    y += h;
                    break;
                case ContentAlignment.BottomCenter:
                    x += w / 2;
                    y += h;
                    break;
                case ContentAlignment.BottomRight:
                    x += w;
                    y += h;
                    break;
            }
            return new Rectangle(new Point(x, y), size);
        }
        #endregion
        #region SizeF
        /// <summary>
        /// Zarovná prostor dané velikosti do daného hostitele v daném zarovnání
        /// </summary>
        /// <param name="size"></param>
        /// <param name="bounds"></param>
        /// <param name="alignment"></param>
        /// <returns></returns>
        public static RectangleF AlignTo(this SizeF size, RectangleF bounds, ContentAlignment alignment, bool cropSize)
        {
            SizeF realSize = size;
            if (cropSize)
            {
                if (realSize.Width > bounds.Width)
                    realSize.Width = bounds.Width;
                if (realSize.Height > bounds.Height)
                    realSize.Height = bounds.Height;
            }
            return realSize.AlignTo(bounds, alignment);
        }
        /// <summary>
        /// Zarovná prostor dané velikosti do daného hostitele v daném zarovnání
        /// </summary>
        /// <param name="size"></param>
        /// <param name="bounds"></param>
        /// <param name="alignment"></param>
        /// <returns></returns>
        public static RectangleF AlignTo(this SizeF size, RectangleF bounds, ContentAlignment alignment)
        {
            float x = bounds.X;
            float y = bounds.Y;
            float w = bounds.Width - size.Width;
            float h = bounds.Height - size.Height;
            switch (alignment)
            {
                case ContentAlignment.TopLeft:
                    break;
                case ContentAlignment.TopCenter:
                    x += w / 2f;
                    break;
                case ContentAlignment.TopRight:
                    x += w;
                    break;
                case ContentAlignment.MiddleLeft:
                    y += h / 2f;
                    break;
                case ContentAlignment.MiddleCenter:
                    x += w / 2f;
                    y += h / 2f;
                    break;
                case ContentAlignment.MiddleRight:
                    x += w;
                    y += h / 2f;
                    break;
                case ContentAlignment.BottomLeft:
                    y += h;
                    break;
                case ContentAlignment.BottomCenter:
                    x += w / 2f;
                    y += h;
                    break;
                case ContentAlignment.BottomRight:
                    x += w;
                    y += h;
                    break;
            }
            return new RectangleF(new PointF(x, y), size);
        }
        #endregion
        #region Color: Shift
        /// <summary>
        /// Vrací danou barvu s daným posunutím
        /// </summary>
        /// <param name="root">Výchozí barva</param>
        /// <param name="shift">Posunutí barvy</param>
        /// <returns></returns>
        public static Color Shift(this Color root, float shift)
        {
            float r = (float)root.R + shift;
            float g = (float)root.G + shift;
            float b = (float)root.B + shift;
            return GetColor(root.A, r, g, b);
        }
        /// <summary>
        /// Vrací danou barvu s daným posunutím
        /// </summary>
        /// <param name="root">Výchozí barva</param>
        /// <param name="shiftR">Posunutí barvy pro složku R</param>
        /// <param name="shiftG">Posunutí barvy pro složku G</param>
        /// <param name="shiftB">Posunutí barvy pro složku B</param>
        /// <returns></returns>
        public static Color Shift(this Color root, float shiftR, float shiftG, float shiftB)
        {
            float r = (float)root.R + shiftR;
            float g = (float)root.G + shiftG;
            float b = (float)root.B + shiftB;
            return GetColor(root.A, r, g, b);
        }
        /// <summary>
        /// Vrací danou barvu s daným posunutím
        /// </summary>
        /// <param name="root">Výchozí barva</param>
        /// <param name="shiftA">Posunutí barvy pro složku A</param>
        /// <param name="shiftR">Posunutí barvy pro složku R</param>
        /// <param name="shiftG">Posunutí barvy pro složku G</param>
        /// <param name="shiftB">Posunutí barvy pro složku B</param>
        /// <returns></returns>
        public static Color Shift(this Color root, float shiftA, float shiftR, float shiftG, float shiftB)
        {
            float a = (float)root.A + shiftA;
            float r = (float)root.R + shiftR;
            float g = (float)root.G + shiftG;
            float b = (float)root.B + shiftB;
            return GetColor(a, r, g, b);
        }
        /// <summary>
        /// Vrací barvu dle daných složek, přičemž složky (a,r,g,b) omezuje do rozsahu 0 - 255.
        /// </summary>
        /// <param name="a"></param>
        /// <param name="r"></param>
        /// <param name="g"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        private static Color GetColor(float a, float r, float g, float b)
        {
            int ac = (a < 0f ? 0 : (a > 255f ? 255 : (int)a));
            int rc = (r < 0f ? 0 : (r > 255f ? 255 : (int)r));
            int gc = (g < 0f ? 0 : (g > 255f ? 255 : (int)g));
            int bc = (b < 0f ? 0 : (b > 255f ? 255 : (int)b));
            return Color.FromArgb(ac, rc, gc, bc);
        }
        #endregion
        #region Color: Change
        /// <summary>
        /// Změní barvu.
        /// Změna (Change) není posun (Shift): shift přičítá / odečítá hodnotu, ale změna hodnotu mění koeficientem.
        /// Pokud je hodnota složky například 170 a koeficient změny 0.25, pak výsledná hodnota je +25% od výchozí hodnoty směrem k maximu (255): 170 + 0.25 * (255 - 170).
        /// Obdobně změna dolů -70% z hodnoty 170 dá výsledek 170 - 0.7 * (170).
        /// </summary>
        /// <param name="root">Výchozí barva</param>
        /// <param name="change">Změna složek</param>
        /// <returns></returns>
        public static Color ChangeColor(this Color root, float change)
        {
            float r = ChangeCC(root.R, change);
            float g = ChangeCC(root.G, change);
            float b = ChangeCC(root.B, change);
            return GetColor(root.A, r, g, b);
        }
        /// <summary>
        /// Změní barvu.
        /// Změna (Change) není posun (Shift): shift přičítá / odečítá hodnotu, ale změna hodnotu mění koeficientem.
        /// Pokud je hodnota složky například 170 a koeficient změny 0.25, pak výsledná hodnota je +25% od výchozí hodnoty směrem k maximu (255): 170 + 0.25 * (255 - 170).
        /// Obdobně změna dolů -70% z hodnoty 170 dá výsledek 170 - 0.7 * (170).
        /// </summary>
        /// <param name="root">Výchozí barva</param>
        /// <param name="changeR">Změna složky R</param>
        /// <param name="changeG">Změna složky R</param>
        /// <param name="changeB">Změna složky R</param>
        /// <returns></returns>
        public static Color ChangeColor(this Color root, float changeR, float changeG, float changeB)
        {
            float r = ChangeCC(root.R, changeR);
            float g = ChangeCC(root.G, changeG);
            float b = ChangeCC(root.B, changeB);
            return GetColor(root.A, r, g, b);
        }
        /// <summary>
        /// Vrátí složku změněnou koeficientem.
        /// </summary>
        /// <param name="colorComponent"></param>
        /// <param name="change"></param>
        /// <returns></returns>
        private static float ChangeCC(int colorComponent, float change)
        {
            float result = (float)colorComponent;
            if (change > 0f)
            {
                result = result + (change * (255f - result));
            }
            else if (change < 0f)
            {
                result = result - (-change * result);
            }
            return result;
        }
        #endregion
        #region Color: Morph
        /// <summary>
        /// Vrací barvu, která je výsledkem interpolace mezi barvou this a barvou other, 
        /// přičemž od barvy this se liší poměrem morph.
        /// Poměr (morph): 0=vrací se výchozí barva (this).
        /// Poměr (morph): 1=vrací se barva cílová (other).
        /// Poměr může být i větší než 1 (pak je výsledek ještě za cílovou barvou other),
        /// anebo může být záporný (pak výsledkem je barva na opačné straně než je other).
        /// </summary>
        /// <param name="root">Výchozí barva</param>
        /// <param name="other">Cílová barva</param>
        /// <param name="morph">Poměr morph (0=vrátí this, 1=vrátí other, hodnota může být záporná i větší než 1f)</param>
        /// <returns></returns>
        public static Color Morph(this Color root, Color other, float morph)
        {
            float a = GetMorph(root.A, other.A, morph);
            float r = GetMorph(root.R, other.R, morph);
            float g = GetMorph(root.G, other.G, morph);
            float b = GetMorph(root.B, other.B, morph);
            return GetColor(a, r, g, b);
        }
        /// <summary>
        /// Vrátí složku barvy vzniklou morphingem = interpolací.
        /// </summary>
        /// <param name="root">Výchozí složka</param>
        /// <param name="other">Cílová složka</param>
        /// <param name="morph">Poměr morph (0=vrátí this, 1=vrátí other, hodnota může být záporná i větší než 1f)</param>
        /// <returns></returns>
        private static float GetMorph(float root, float other, float morph)
        {
            float dist = other - root;
            return root + morph * dist;
        }
        #endregion
        #region Color: Contrast
        /// <summary>
        /// Vrátí kontrastní barvu černou nebo bílou k barvě this.
        /// </summary>
        /// <param name="root">Výchozí barva</param>
        /// <returns></returns>
        public static Color Contrast(this Color root)
        {
            float a = root.A;
            int q = 184;
            // Určím, kolik složek je světlejších než (q):
            int c = (root.R >= q ? 1 : 0) +
                    (root.G >= q ? 1 : 0) +
                    (root.B >= q ? 1 : 0);
            // Pokud jsou dvě nebo tři složky světlejší než (q), pak vracím černou, jinak bílou:
            return (c >= 2 ? Color.Black : Color.White);
        }

        /// <summary>
        /// Vrátí barvu, která je kontrastní vůči barvě this.
        /// Kontrastní barva leží o dané množství barvy směrem k protilehlé barvě (vždy na opačnou stranu od hodnoty 128), v každé složce zvlášť.
        /// Například ke složce s hodnotou 160 je kontrastní barvou o 32 hodnota (160-32) = 128, k hodnotě 100 o 32 je kontrastní (100+32) = 132.
        /// Tedy kontrastní barva k barvě ((rgb: 64,96,255), contrast=32) je barva: rgb(96,128,223) = (64+32, 96+32, 255-32).
        /// Složka A se nemění.
        /// </summary>
        /// <param name="root">Výchozí barva</param>
        /// <param name="contrast">Míra kontrastu</param>
        /// <returns></returns>
        public static Color Contrast(this Color root, int contrast)
        {
            float a = root.A;
            float r = GetContrast(root.R, contrast);
            float g = GetContrast(root.G, contrast);
            float b = GetContrast(root.B, contrast);
            return GetColor(a, r, g, b);
        }
        /// <summary>
        /// Vrátí barvu, která je kontrastní vůči barvě this.
        /// Kontrastní barva leží o dané množství barvy směrem k protilehlé barvě (vždy na opačnou stranu od hodnoty 128), v každé složce zvlášť.
        /// Například ke složce s hodnotou 160 je kontrastní barvou o 32 hodnota (160-32) = 128, k hodnotě 100 o 32 je kontrastní (100+32) = 132.
        /// Tedy kontrastní barva k barvě ((rgb: 64,96,255), contrast=32) je barva: rgb(96,128,223) = (64+32, 96+32, 255-32).
        /// Složka A se nemění.
        /// </summary>
        /// <param name="root">Výchozí barva</param>
        /// <param name="contrastR">Míra kontrastu ve složce R</param>
        /// <param name="contrastG">Míra kontrastu ve složce G</param>
        /// <param name="contrastB">Míra kontrastu ve složce B</param>
        /// <returns></returns>
        public static Color Contrast(this Color root, int contrastR, int contrastG, int contrastB)
        {
            float a = root.A;
            float r = GetContrast(root.R, contrastR);
            float g = GetContrast(root.G, contrastG);
            float b = GetContrast(root.B, contrastB);
            return GetColor(a, r, g, b);
        }
        /// <summary>
        /// Vrací kontrastní složku
        /// </summary>
        /// <param name="root"></param>
        /// <param name="contrast"></param>
        /// <returns></returns>
        private static float GetContrast(int root, int contrast)
        {
            return (root <= 128 ? root + contrast : root - contrast);
        }
        #endregion
        #region Type
        /// <summary>
        /// Vrátí Namespace.Name daného typu
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static string NamespaceName(this Type type)
        {
            return type.Namespace + "." + type.Name;
        }
        #endregion
        #region XElement
        public static void AddAttribute(this XElement element, string attributeName, string value)
        {
            element.Add(new XAttribute(attributeName, (value == null ? "" : value)));
        }
        public static void AddAttribute(this XElement element, string attributeName, int value)
        {
            element.Add(new XAttribute(attributeName, Convertor.Int32ToString(value)));
        }
        public static void AddAttribute(this XElement element, string attributeName, decimal value)
        {
            element.Add(new XAttribute(attributeName, Convertor.DecimalToString(value)));
        }
        public static void AddAttribute(this XElement element, string attributeName, bool value)
        {
            element.Add(new XAttribute(attributeName, (value ? "1" : "0")));
        }
        public static void AddAttribute(this XElement element, string attributeName, DateTime value)
        {
            element.Add(new XAttribute(attributeName, Convertor.DateTimeToString(value)));
        }
        public static void AddAttribute(this XElement element, string attributeName, Rectangle value)
        {
            element.Add(new XAttribute(attributeName, Convertor.RectangleToString(value)));
        }
        public static string GetAttributeString(this XElement element, string attributeName)
        {
            XAttribute attr = GetXAttribute(element, attributeName);
            if (attr == null) return null;
            return attr.Value;
        }
        public static Int32 GetAttributeInt32(this XElement element, string attributeName)
        {
            XAttribute attr = GetXAttribute(element, attributeName);
            if (attr == null) return 0;
            return (int)Convertor.StringToInt32(attr.Value);
        }
        public static decimal GetAttributeDecimal(this XElement element, string attributeName)
        {
            XAttribute attr = GetXAttribute(element, attributeName);
            if (attr == null) return 0m;
            return (decimal)Convertor.StringToDecimal(attr.Value);
        }
        public static bool GetAttributeBoolean(this XElement element, string attributeName)
        {
            XAttribute attr = GetXAttribute(element, attributeName);
            if (attr == null) return false;
            return (attr.Value == "1" || attr.Value.ToUpper() == "Y" || attr.Value.ToLower() == "true" || attr.Value.ToLower() == "yes" || attr.Value.ToUpper() == "A");
        }
        public static DateTime GetAttributeDateTime(this XElement element, string attributeName)
        {
            XAttribute attr = GetXAttribute(element, attributeName);
            if (attr == null) return DateTime.MinValue;
            return (DateTime)Convertor.StringToDateTime(attr.Value);
        }
        public static Rectangle GetAttributeRectangle(this XElement element, string attributeName)
        {
            XAttribute attr = GetXAttribute(element, attributeName);
            if (attr == null) return Rectangle.Empty;
            return (Rectangle)Convertor.StringToRectangle(attr.Value);
        }

        private static XAttribute GetXAttribute(XElement element, string attributeName)
        {
            if (element == null) return null;
            XAttribute attr = element.Attribute(attributeName);
            return attr;
        }
        public static void Save(this XDocument xDoc, string file, bool textFormat)
        {
            System.Xml.XmlWriterSettings settings = new System.Xml.XmlWriterSettings();
            settings.ConformanceLevel = System.Xml.ConformanceLevel.Document;
            settings.Encoding = Encoding.UTF8;
            settings.Indent = textFormat;
            settings.IndentChars = "  ";
            settings.NewLineHandling = System.Xml.NewLineHandling.Entitize;
            settings.NewLineChars = Environment.NewLine;
            settings.NewLineOnAttributes = false;
            settings.OmitXmlDeclaration = false;
            using (System.Xml.XmlWriter writter = System.Xml.XmlWriter.Create(file, settings))
            {
                xDoc.Save(writter);
            }
        }
        #endregion
    }
}
